/*
 * libkir: a transition library for -DKEEP_IT_REAL
 *
 * Michael Brown <mbrown@fensystems.co.uk>
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )

/****************************************************************************
 * This file defines libkir: an interface between external and
 * internal environments when -DKEEP_IT_REAL is used, so that both
 * internal and external environments are in real mode.  It deals with
 * switching data segments and the stack.  It provides the following
 * functions:
 *
 * ext_to_kir &		switch between external and internal (kir)
 * kir_to_ext		environments, preserving all non-segment
 *			registers
 *
 * kir_call		issue a call to an internal routine from external
 *			code
 *
 * libkir is written to avoid assuming that segments are anything
 * other than opaque data types, and also avoids assuming that the
 * stack pointer is 16-bit.  This should enable it to run just as well
 * in 16:16 or 16:32 protected mode as in real mode.
 ****************************************************************************
 */

/* Breakpoint for when debugging under bochs */
#define BOCHSBP xchgw %bx, %bx

	.section ".note.GNU-stack", "", @progbits
	.text
	.arch i386
	.section ".text16", "awx", @progbits
	.code16
	
/****************************************************************************
 * init_libkir (real-mode or 16:xx protected-mode far call)
 *
 * Initialise libkir ready for transitions to the kir environment
 *
 * Parameters:
 *   %cs : .text16 segment
 *   %ds : .data16 segment
 ****************************************************************************
 */
	.globl	init_libkir
init_libkir:
	/* Record segment registers */
	pushw	%ds
	popw	%cs:kir_ds
	lret
	
/****************************************************************************
 * ext_to_kir (real-mode or 16:xx protected-mode near call)
 *
 * Switch from external stack and segment registers to internal stack
 * and segment registers.  %ss:sp is restored from the saved kir_ds
 * and kir_sp.  %ds, %es, %fs and %gs are all restored from the saved
 * kir_ds.  All other registers are preserved.
 *
 * %cs:0000 must point to the start of the runtime image code segment
 * on entry.
 *
 * Parameters: none
 ****************************************************************************
 */

	.globl	ext_to_kir
ext_to_kir:
	/* Record external segment registers */
	movw	%ds, %cs:ext_ds
	pushw	%cs
	popw	%ds	/* Set %ds = %cs for easier access to variables */
	movw	%es, %ds:ext_es
	movw	%fs, %ds:ext_fs
	movw	%gs, %ds:ext_fs

	/* Preserve registers */
	movw	%ax, %ds:save_ax

	/* Extract near return address from stack */
	popw	%ds:save_retaddr

	/* Record external %ss:esp */
	movw	%ss, %ds:ext_ss
	movl	%esp, %ds:ext_esp

	/* Load internal segment registers and stack pointer */
	movw	%ds:kir_ds, %ax
	movw	%ax, %ss
	movzwl	%ds:kir_sp, %esp
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs
1:

	/* Place return address on new stack */
	pushw	%cs:save_retaddr
	
	/* Restore registers and return */
	movw	%cs:save_ax, %ax
	ret

/****************************************************************************
 * kir_to_ext (real-mode or 16:xx protected-mode near call)
 *
 * Switch from internal stack and segment registers to external stack
 * and segment registers.  %ss:%esp is restored from the saved ext_ss
 * and ext_esp.  Other segment registers are restored from the
 * corresponding locations.  All other registers are preserved.
 *
 * Note that it is actually %ss that is recorded as kir_ds, on the
 * assumption that %ss == %ds when kir_to_ext is called.
 *
 * Parameters: none
 ****************************************************************************
 */

	.globl	kir_to_ext
kir_to_ext:
	/* Record near return address */
	pushw	%cs
	popw	%ds	/* Set %ds = %cs for easier access to variables */
	popw	%ds:save_retaddr
	
	/* Record internal segment registers and %sp */
	movw	%ss, %ds:kir_ds
	movw	%sp, %ds:kir_sp

	/* Load external segment registers and stack pointer */
	movw	%ds:ext_ss, %ss
	movl	%ds:ext_esp, %esp
	movw	%ds:ext_gs, %gs
	movw	%ds:ext_fs, %fs
	movw	%ds:ext_es, %es
	movw	%ds:ext_ds, %ds

	/* Return */
	pushw	%cs:save_retaddr
	ret
	
/****************************************************************************
 * kir_call (real-mode or 16:xx protected-mode far call)
 *
 * Call a specific C function in the internal code.  The prototype of
 * the C function must be
 *   void function ( struct i386_all_resg *ix86 ); 
 * ix86 will point to a struct containing the real-mode registers
 * at entry to kir_call.
 *
 * All registers will be preserved across kir_call(), unless the C
 * function explicitly overwrites values in ix86.  Interrupt status
 * will also be preserved.
 *
 * Parameters:
 *   function : (32-bit) virtual address of C function to call
 *
 * Example usage:
 *	pushl	$pxe_api_call
 *	lcall	$UNDI_CS, $kir_call
 *	addw	$4, %sp
 * to call in to the C function
 *      void pxe_api_call ( struct i386_all_regs *ix86 );
 ****************************************************************************
 */

	.globl	kir_call
kir_call:
	/* Preserve flags.  Must do this before any operation that may
	 * affect flags.
	 */
	pushfl
	popl	%cs:save_flags

	/* Disable interrupts.  We do funny things with the stack, and
	 * we're not re-entrant.
	 */
	cli
		
	/* Extract address of internal routine from stack.  We must do
	 * this without using (%bp), because we may be called with
	 * either a 16-bit or a 32-bit stack segment.
	 */
	popl	%cs:save_retaddr	/* Scratch location */
	popl	%cs:save_function
	subl	$8, %esp		/* Restore %esp */
	
	/* Switch to internal stack.  Note that the external stack is
	 * inaccessible once we're running internally (since we have
	 * no concept of 48-bit far pointers)
	 */
	call	ext_to_kir
	
	/* Store external registers on internal stack */
	pushl	%cs:save_flags
	pushal
	pushl	%cs:ext_fs_and_gs
	pushl	%cs:ext_ds_and_es
	pushl	%cs:ext_cs_and_ss

	/* Push &ix86 on stack and call function */
	sti
	pushl	%esp
	data32 call *%cs:save_function
	popl	%eax /* discard */
	
	/* Restore external registers from internal stack */
	popl	%cs:ext_cs_and_ss
	popl	%cs:ext_ds_and_es
	popl	%cs:ext_fs_and_gs
	popal
	popl	%cs:save_flags

	/* Switch to external stack */
	call	kir_to_ext

	/* Restore flags */
	pushl	%cs:save_flags
	popfl

	/* Return */
	lret

/****************************************************************************
 * Stored internal and external stack and segment registers
 ****************************************************************************
 */
	
ext_cs_and_ss:	
ext_cs:		.word 0
ext_ss:		.word 0
ext_ds_and_es:	
ext_ds:		.word 0
ext_es:		.word 0
ext_fs_and_gs:	
ext_fs:		.word 0
ext_gs:		.word 0
ext_esp:	.long 0

		.globl kir_ds
kir_ds:		.word 0
		.globl kir_sp
kir_sp:		.word _estack

/****************************************************************************
 * Temporary variables
 ****************************************************************************
 */
save_ax:	.word 0
save_retaddr:	.long 0
save_flags:	.long 0
save_function:	.long 0
